﻿//-----------------------------------------------------------------------
// <copyright file="Character.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using GateShift.Common.Repository;
using GateShift.Common.Repository.Business;
using GateShift.Common.Repository.Interfaces.DataInterfaces;
using GateShift.Common.Repository.Interfaces.Factories;
using GateShift.Common.Repository.Interfaces.Repositories;

#endregion

namespace GateShift.Services.GameFramework.GameObjects
{
    /// <summary>
    /// Business class for managing all character related activity
    /// </summary>
    public class Character : IdentityBusinessBase<ICharacterData, ICharacterRepository>
    {
        #region Fields

        private Race _race; // DO NOT reference unless setting to null. Use the property that is lazy loaded
        private List<CharacterClass> _classes; // DO NOT reference unless setting to null. Use the property that is lazy loaded
        private Account _account; // DO NOT reference unless setting to null. Use the property that is lazy loaded

        #endregion

        #region CTor/DTor

        #region Creation Routines

        /// <summary>
        /// Initializes a new instance of the Character class
        /// </summary>
        public Character()
            : base(new RepositoryFactory())
        {
        }

        /// <summary>
        /// Initializes a new instance of the Character class
        /// </summary>
        /// <param name="Factory">Factory for retrieving the Repository from</param>
        /// <exception cref="ArgumentNullException">Thrown if Factory is null</exception>
        public Character(IRepositoryFactory Factory)
            : base(Factory)
        {
        }

        #endregion

        #region Retrieve by ID

        /// <summary>
        /// Initializes a new instance of the Character class
        /// </summary>
        /// <param name="ID">Identity of the data to retrieve</param>
        /// <exception cref="ArgumentNullException">Thrown if ID is null</exception>
        public Character(Identity ID)
            : base(new RepositoryFactory(), ID)
        {
        }

        /// <summary>
        /// Initializes a new instance of the Character class
        /// </summary>
        /// <param name="factory">Factory for retrieving the Repository from</param>
        /// <param name="ID">Identity of the data to retrieve</param>
        /// <exception cref="ArgumentNullException">Thrown if factory or ID are null</exception>
        public Character(IRepositoryFactory factory, Identity ID)
            : base(factory, ID)
        {
        }

        #endregion

        #region Retrieve by Data

        /// <summary>
        /// Initializes a new instance of the Character class
        /// </summary>
        /// <param name="factory">Factory for retrieving the Repository from</param>
        /// <param name="data">ICharacter of the data to retrieve</param>
        /// <exception cref="ArgumentNullException">Thrown if Factory or ID are null</exception>
        private Character(IRepositoryFactory factory, ICharacterData data)
            : base(factory, data)
        {
        }

        #endregion

        #endregion

        #region Lazy Loaded Properties

        /// <summary>
        /// Gets the account associated with this character
        /// </summary>
        public Account Account
        {
            get
            {
                if (_account == null && AccountID.HasValue)
                {
                    _account = new Account(Factory, AccountID.Value);
                }

                return _account;
            }
        }

        /// <summary>
        /// Gets the race associated with this character
        /// </summary>
        public Race Race
        {
            get
            {
                if (_race == null && RaceID.HasValue)
                {
                    _race = new Race(Factory, RaceID.Value);
                }

                return _race;
            }
        }

        /// <summary>
        /// Gets a collection for all the classes associated with this character and their ranks
        /// </summary>
        public List<CharacterClass> Classes
        {
            get
            {
                if (_classes == null)
                {
                    _classes = CharacterClass.RetrieveCharacterClasses(Factory, Data.ID);
                }

                return _classes;
            }
        }

        #endregion

        #region Identifier Properties

        /// <summary>
        /// Gets or sets the associated Race Identity
        /// </summary>
        public Identity? RaceID
        {
            get
            {
                return Data.RaceID;
            }

            set
            {
                if (value.HasValue)
                {
                    throw new NullReferenceException("Character must have a valid race. Value was null.");
                }

                Data.RaceID = value;
                _race = null; // Force reload next call of race property
            }
        }

        /// <summary>
        /// Gets or sets the associated account for this character
        /// </summary>
        public Identity? AccountID
        {
            get
            {
                return Data.AccountID;
            }

            set
            {
                if (value.HasValue)
                {
                    throw new NullReferenceException("Character must have a valid account. Value was null.");
                }

                Data.AccountID = value;
                _account = null;
            }
        }

        #endregion

        #region Base Attributes

        /// <summary>
        /// Gets or sets the character's base Accuracy
        /// </summary>
        public byte BaseAccuracy
        {
            get
            {
                return Data.BaseAccuracy;
            }

            set
            {
                Data.BaseAccuracy = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Agility
        /// </summary>
        public byte BaseAgility
        {
            get
            {
                return Data.BaseAgility;
            }

            set
            {
                Data.BaseAgility = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Endurance
        /// </summary>
        public byte BaseEndurance
        {
            get
            {
                return Data.BaseEndurance;
            }

            set
            {
                Data.BaseEndurance = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Energy
        /// </summary>
        public byte BaseEnergy
        {
            get
            {
                return Data.BaseEnergy;
            }

            set
            {
                Data.BaseEnergy = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Force
        /// </summary>
        public byte BaseForce
        {
            get
            {
                return Data.BaseForce;
            }

            set
            {
                Data.BaseForce = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Insight
        /// </summary>
        public byte BaseInsight
        {
            get
            {
                return Data.BaseInsight;
            }

            set
            {
                Data.BaseInsight = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Intellect
        /// </summary>
        public byte BaseIntellect
        {
            get
            {
                return Data.BaseIntellect;
            }

            set
            {
                Data.BaseIntellect = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Strength
        /// </summary>
        public byte BaseStrength
        {
            get
            {
                return Data.BaseStrength;
            }

            set
            {
                Data.BaseStrength = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's base Will
        /// </summary>
        public byte BaseWill
        {
            get
            {
                return Data.BaseWill;
            }

            set
            {
                Data.BaseWill = value;
            }
        }

        #endregion

        #region ReadWrite Properties

        /// <summary>
        /// Gets or sets the name of the character
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if the name exists in the database for another character</exception>
        /// <exception cref="FormatException">Thown if the name is invalid</exception>
        /// <exception cref="ArgumentNullException">Thrown if the name is NULL or empty</exception>
        /// <exception cref="IndexOutOfRangeException">Thrown if the length is more than 20 characters</exception>
        public string CharacterName
        {
            get
            {
                return Data.CharacterName;
            }

            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentNullException("value", "A name must be specified");
                }

                if (value.Length > 20)
                {
                    throw new IndexOutOfRangeException(string.Format("Name is more than 20 Characters. Currently: {0}", value.Length));
                }

                if (!IsCharacterNameValid(value))
                {
                    throw new FormatException("Character name format is invalid. Check with IsCharacterNameValid.");
                }

                if (CharacterNameExists(value, ID))
                {
                    throw new InvalidOperationException(
                        "Character name already exists on another character in the system.");
                }

                Data.CharacterName = value;
            }
        }

        /// <summary>
        /// Gets or sets the character's total credit value
        /// </summary>
        public long Money
        {
            get
            {
                return Data.Money;
            }

            set
            {
                Data.Money = value;
            }
        }

        #endregion

        #region Calculated Attributes

        /// <summary>
        /// Gets the character's total Accuracy
        /// </summary>
        public short TotalAccuracy
        {
            get
            {
                short returnValue = Data.BaseAccuracy;

                if (Race != null)
                {
                    returnValue += Race.ModifierAccuracy;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalAccuracy;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Agility
        /// </summary>
        public short TotalAgility
        {
            get
            {
                short returnValue = Data.BaseAgility;

                if (Race != null)
                {
                    returnValue += Race.ModifierAgility;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalAgility;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Endurance
        /// </summary>
        public short TotalEndurance
        {
            get
            {
                short returnValue = Data.BaseEndurance;

                if (Race != null)
                {
                    returnValue += Race.ModifierEndurance;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalEndurance;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Energy
        /// </summary>
        public short TotalEnergy
        {
            get
            {
                short returnValue = Data.BaseEnergy;

                if (Race != null)
                {
                    returnValue += Race.ModifierEnergy;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalEnergy;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Force
        /// </summary>
        public short TotalForce
        {
            get
            {
                short returnValue = Data.BaseForce;

                if (Race != null)
                {
                    returnValue += Race.ModifierForce;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalForce;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Insight
        /// </summary>
        public short TotalInsight
        {
            get
            {
                short returnValue = Data.BaseInsight;

                if (Race != null)
                {
                    returnValue += Race.ModifierInsight;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalInsight;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Intellect
        /// </summary>
        public short TotalIntellect
        {
            get
            {
                short returnValue = Data.BaseIntellect;

                if (Race != null)
                {
                    returnValue += Race.ModifierIntellect;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalIntellect;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Strength
        /// </summary>
        public short TotalStrength
        {
            get
            {
                short returnValue = Data.BaseStrength;

                if (Race != null)
                {
                    returnValue += Race.ModifierStrength;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalStrength;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the character's total Will
        /// </summary>
        public short TotalWill
        {
            get
            {
                short returnValue = Data.BaseWill;

                if (Race != null)
                {
                    returnValue += Race.ModifierWill;
                }

                if (Classes != null)
                {
                    foreach (CharacterClass classMap in Classes)
                    {
                        returnValue += classMap.TotalWill;
                    }
                }

                return returnValue;
            }
        }

        #endregion

        #region Public Static Factory Methods

        /// <summary>
        /// Retrieves the Character for the requested character name
        /// </summary>
        /// <param name="characterName">The name of the Character to retrieve</param>
        /// <returns>Character of matching result or null if no Character exists</returns>
        /// <exception cref="ArgumentNullException">Thrown if characterName is null</exception>
        public static Character RetrieveCharacterByName(string characterName)
        {
            return RetrieveCharacterByName(new RepositoryFactory(), characterName);
        }

        /// <summary>
        /// Checks to see if the requested character name exists in the database
        /// </summary>
        /// <param name="characterName">Character Name to search for</param>
        /// <returns>Boolean whether or not the specified character name exists</returns>
        /// <exception cref="ArgumentNullException">Thrown if characterName is null</exception>
        public static bool CharacterNameExists(string characterName)
        {
            return CharacterNameExists(new RepositoryFactory(), characterName);
        }

        /// <summary>
        /// Checks to see if the requested character name exists in the database, using an optional identity of a character to ignore
        /// </summary>
        /// <param name="characterName">Character Name to search for</param>
        /// <param name="ignoreID">Identity of a character to ignore during the search (Optional)</param>
        /// <returns>Boolean whether or not the specified character name exists</returns>
        /// <exception cref="ArgumentNullException">Thrown if characterName is null</exception>
        public static bool CharacterNameExists(string characterName, Identity ignoreID)
        {
            return CharacterNameExists(new RepositoryFactory(), characterName, ignoreID);
        }

        #endregion

        #region Public Static Validation Methods

        /// <summary>
        /// Tests whether or not a specified Character name is considered valid
        /// </summary>
        /// <param name="characterName">Character Name to validate</param>
        /// <returns>Boolean of whether or not the account is valid</returns>
        /// <remarks> 1) Alphanumeric 2) First Character must be a letter 3) Minimum 4 and maximum 20 characters</remarks>
        public static bool IsCharacterNameValid(string characterName)
        {
            // First position must be alpha based, everything else can be alphanumeric and max of 20 characters and min of 4
            Regex check = new Regex("^[a-zA-Z]{1}[a-zA-Z0-9]{3,19}$");

            return check.IsMatch(characterName);
        }

        #endregion

        #region Public Validations

        /// <summary>
        /// Validates the Character is valid
        /// </summary>
        /// <returns>Boolean based on the validity of the object</returns>
        public override bool IsValid()
        {
            // Check required fields
            if (string.IsNullOrEmpty(Data.CharacterName))
            {
                return false;
            }

            // TODO : Other values

            // Check the Values
            if (!IsCharacterNameValid(Data.CharacterName))
            {
                return false;
            }

            if (CharacterNameExists(Data.CharacterName, Data.ID))
            {
                return false;
            }

            // Everything passed by this point!
            return true;
        }

        #endregion

        #region Internal Static Factory Methods

        /// <summary>
        /// Retrieves the Character for the requested character name
        /// </summary>
        /// <param name="factory">Factory for retrieving the CharacterClasses from</param>
        /// <param name="characterName">The name of the Character to retrieve</param>
        /// <returns>Character of matching result or null if no Character exists</returns>
        /// <exception cref="ArgumentNullException">Thrown if either factory or characterName are null</exception>
        internal static Character RetrieveCharacterByName(IRepositoryFactory factory, string characterName)
        {
            if (factory == null)
            {
                throw new ArgumentNullException("factory", "Factory cannot be null to retrieve from");
            }

            if (characterName == null)
            {
                throw new ArgumentNullException("characterName", "CharacterName cannot be null to retrieve by");
            }

            ICharacterData character = factory.GetRepository<ICharacterRepository>().RetrieveByCharacterName(characterName);

            return character != null ? new Character(factory, character) : null;
        }

        /// <summary>
        /// Checks to see if the requested character name exists in the database
        /// </summary>
        /// <param name="factory">IRepositoryFactory to use for retrieving data from</param>
        /// <param name="characterName">Character Name to search for</param>
        /// <returns>Boolean whether or not the specified character name exists</returns>
        /// <exception cref="ArgumentNullException">Thrown if factory or characterName are null</exception>
        internal static bool CharacterNameExists(IRepositoryFactory factory, string characterName)
        {
            return CharacterNameExists(factory, characterName, null);
        }

        /// <summary>
        /// Checks to see if the requested character name exists in the database, using an optional identity of a character to ignore
        /// </summary>
        /// <param name="factory">IRepositoryFactory to use for retrieving data from</param>
        /// <param name="characterName">Character Name to search for</param>
        /// <param name="ignoreID">Identity of a character to ignore during the search (Optional)</param>
        /// <returns>Boolean whether or not the specified character name exists</returns>
        /// <exception cref="ArgumentNullException">Thrown if factory or characterName are null</exception>
        internal static bool CharacterNameExists(IRepositoryFactory factory, string characterName, Identity? ignoreID)
        {
            if (factory == null)
            {
                throw new ArgumentNullException("factory", "Factory cannot be null to retrieve from");
            }

            if (characterName == null)
            {
                throw new ArgumentNullException("characterName", "CharacterName cannot be null to retrieve by");
            }

            return factory.GetRepository<ICharacterRepository>().CharacterNameExists(characterName, ignoreID);
        }

        #endregion
    }
}